# ---------------------------------------------------------------------------
# Author: Keith Hill
# Desc:   A CD replacement function that handles history and - and + as well
#         as ..[.]*.  Also provides a prompt function.
# Date:   Nov 18, 2006
# Site:   http://www.codeplex.com/PowerShellCX
# Usage:  In your profile place the following command:
#  
#         . "$Env:PscxHome\Profile\CD.ps1"
#
#         If you do not want this function to update your console window's
#         title bar then set $PscxCdWindowTitleUpdatePreference to $false.
#       
#         If you want to have a string always prefixed to the console
#         window's title bar before the path, then set the variable
#         $PscxCdWindowTitlePrefixPreference to the desired prefix.
# ---------------------------------------------------------------------------
$global:__pscxCdBackwardStack > $null
$global:__pscxCdForewardStack > $null

Remove-Item alias:cd -Force -ErrorAction:SilentlyContinue
function cd ([string]$path) {
    # If more than one arg, let set-location handle this error
    if ($args.Length -gt 0) {
        write-error "Takes only one argument."
        return 
    }
    
    # Handle the rest of the commands plus a path arg
    if ($path -eq "?") {
        ""
        "Usage: cd [path <or> command]"
        "Commands:"
        "    <no arg> : Display the directory stack"
        "    ~        : Change directory to your `$Home directory"
        "    -        : Change directory to the previous directory in the stack"
        "    +        : Change directory to the next directory in the stack"
        "    -number  : Change directory to the specified directory in the stack"
        "    ..[.]*   : Change directory to a parent directory of the current directory"
        "    ?        : Display this usage information"
        ""
        return
    }
    
    # If not created, initialize ArrayList to track directories
    if ($global:__pscxCdBackwardStack -eq $null) {
        $global:__pscxCdBackwardStack = new-object "System.Collections.ArrayList"
        $global:__pscxCdForewardStack = new-object "System.Collections.ArrayList"
    }
    
    $curPath = $(get-location)

    # Dump the directory stack
    if (!$path) {
        # Command to dump the back & fore stacks
        ""
        "     # Directory Stack:"
        "   --- ----------------"
        if ($global:__pscxCdBackwardStack.Count -ge 0) {
            for ($i = 0; $i -lt $global:__pscxCdBackwardStack.Count; $i++) { 
                "   {0,3} {1}" -f $i, $global:__pscxCdBackwardStack[$i]
            } 
        }

        "-> {0,3} {1}" -f $i++, $curPath

        if ($global:__pscxCdForewardStack.Count -ge 0) {
            $ndx = $i
            for ($i = 0; $i -lt $global:__pscxCdForewardStack.Count; $i++) { 
                "   {0,3} {1}" -f ($ndx+$i), $global:__pscxCdForewardStack[$i]
            } 
        }
        ""
        return
    }

    # Expand ..[.]+ out to ..\..[\..]+
    if ($path -like "*...*") {
        $regex = new-object System.Text.RegularExpressions.regex("\.\.\.")
        while ($regex.ismatch($path)) {
            $path = $regex.Replace($path, "..\..")
        }
    }

    if ($path -eq "-") {
        if ($global:__pscxCdBackwardStack.Count -eq 0) {
            Write-Host "`nThe backward stack is empty.`n"
        }
        else {        
            $lastNdx = $global:__pscxCdBackwardStack.Count - 1
            $prevPath = $global:__pscxCdBackwardStack[$lastNdx]
            set-location $prevPath
            [void]$global:__pscxCdForewardStack.Insert(0, $curPath)
            $global:__pscxCdBackwardStack.RemoveAt($lastNdx)
        }
    }
    elseif ($path -eq "+") {
        if ($global:__pscxCdForewardStack.Count -eq 0) {
            Write-Host "`nThe foreward stack is empty.`n"
        }
        else {
            $nextPath = $global:__pscxCdForewardStack[0]
            set-location $nextPath    
            [void]$global:__pscxCdBackwardStack.Add($curPath)
            $global:__pscxCdForewardStack.RemoveAt(0)
        }
    }
    elseif ($path -like "-[0-9]*")
    {
        [int]$num = $path.replace("-","")
        $backstackSize = $global:__pscxCdBackwardStack.Count
        $forestackSize = $global:__pscxCdForewardStack.Count
        if ($num -eq $backstackSize) {
            Write-Host "`nWhere ever you go, there you are!`n"
        }
        elseif ($num -lt $backstackSize) {
            $selectedPath = $global:__pscxCdBackwardStack[$num]
            set-location $selectedPath
            [void]$global:__pscxCdForewardStack.Insert(0, $curPath)
            $global:__pscxCdBackwardStack.RemoveAt($num)
            
            [int]$ndx = $num
            [int]$count = $global:__pscxCdBackwardStack.Count - $ndx
            if ($count -gt 0) {
                $itemsToMove = $global:__pscxCdBackwardStack.GetRange($ndx, $count)
                $global:__pscxCdForewardStack.InsertRange(0, $itemsToMove)
                $global:__pscxCdBackwardStack.RemoveRange($ndx, $count)
            }
        }
        elseif (($num -gt $backstackSize) -and ($num -lt ($backstackSize + 1 + $forestackSize))) {
            [int]$ndx = $num - ($backstackSize + 1)
            $selectedPath = $global:__pscxCdForewardStack[$ndx]
            Set-Location $selectedPath
            [void]$global:__pscxCdBackwardStack.Add($curPath)
            $global:__pscxCdForewardStack.RemoveAt($ndx)
            
            [int]$count = $ndx
            if ($count -gt 0) {
                $itemsToMove = $global:__pscxCdForewardStack.GetRange(0, $count)
                $global:__pscxCdBackwardStack.InsertRange(($global:__pscxCdBackwardStack.Count), $itemsToMove)
                $global:__pscxCdForewardStack.RemoveRange(0, $count)
            }
        }
        else {
            Write-Host "`n$num is out of range.`n"
        }
    }
    else
    {
		# CD to specified path
        Set-Location $path -ErrorAction:stop
        $global:__pscxCdForewardStack.Clear();
        [void]$global:__pscxCdBackwardStack.Add($curPath)
    }
}
